
前一篇 useEffect 大概了解之後,我們這篇來簡單了解一下 dependecies (就是辣個 []) 以及 useCallback 吧!
前一篇:https://codesandbox.io/s/03-02-useeffect-jkvtb9
本篇:https://codesandbox.io/s/04-usecallback-5hjnnp?file=/src/Score.js
(突然想到多拉A夢也是藍色的)
前一篇我們知道,useEffect 中提供的 dependecies(依賴) 來判斷需不需要執行。當我們不論是更改 minScore 或是執行 fetchData(),這個 component 都會重新執行,我們可以放一組 Math.random() 來看看輸出,不論更改分數或重新抓取資料,輸出的 random 每次都不一樣,除此之外,我們的在裡面所建立的 function 也都不一樣!
const random = Math.random()
console.log(random) //每次都不一樣
//我也不會都是同一個 function
const fetchData = async () => {
setIsPending(true);
setList([]);
const data = await getPlayerList({ minScore });
setList(data);
setIsPending(false);
};
上一次,我們透過更改 minScore 來使 useEffect 重新取得資料,而 React 是使用 Object.is 來比對[]裡面的值有沒有更新。
Object.is 可以想像成進階版的
===

還記得前一篇的 DEMO 中出現的黃色毛毛蟲嗎? (還是綠色?)
這時候我們可以先嘗試依照 eslint 的提示把 fetchData 也加入 []:
React.useEffect(() => {
//略
}, [minScore, fetchData]);

很好,現在開始進行無限的 loop 了 (╬゚д゚)
回到先前講述到 Object.is ,fetchData 裡面包含了 state(ex: list, isPending) 的控制,而 fetchData 每次render 都會是不同的 instance,放入到 [] 中自然比對上就會是不一樣的了,也同時形成了 loop,這時候有兩種做法:
useCallback
fetchData() 放到 useEffect 裡面const memorizedFunction = useCallback(() => {
//...
}, [])
or
const func = () => {...}
const memorizedFunction = useCallback(func, [])
這邊的
[]與useEffect的[]是一樣的功能
我們就照著這樣的語法給他~套起乃!
const fetchData = React.useCallback(async () => {
setIsPending(true);
setList([]);
const data = await getPlayerList({ minScore });
setList(data);
setIsPending(false);
},[]);
這樣一來就不會 loop 了!
不過這樣也換這個 function 出現毛毛蟲啦! (怒)
useCallback 把這個 fucntion 記起來,但現在的 fetchData() 不論怎麼取資料,由於已經被記憶住了,因此 minScore 在這個 function 裡面的值會永遠是 0,其他的呢? setState 有 React 保障,不論如何都會是同一個 function ; 而我們的 getPlayerList() 也是在 component 外引進來; data 本身只存活在這個 scope,這樣一來……,就是 minScore 了!
比照辦理,我們也可以把 minScore 進一步加進[]:
const fetchData = React.useCallback(async () => {
//略
},[minScore]); //加入minScore
我們也可以把 minScore 改以參數的方式傳進來(如下),就不用擔心記住的問題, fetchData() 也可以一直保持著同一個 function:
const fetchData = React.useCallback(async (minScore) => {
//前略
const data = await getPlayerList({ minScore });
//後略
},[]);
useEffect(()=> {
//..前略
fetchData(minScore)
//..後略
},[minScore])
(請用台語念這個標題)
若不使用 useCallback,也可以把 fetchData() 移駕到 useEffect 裡面使用:
React.useEffect(() => {
//移駕近來
const fetchData = async () => {
//略
};
const timeout = setTimeout(() => {
console.log("fetching...");
fetchData();
}, 1000);
return () => clearTimeout(timeout);
}, [minScore]);
運作下來也沒問題,太讚啦!
「看起來 useCallback 真的很方便,那就把所有 function 都套上去使用吧!」
當然是不行,useCallback 雖然能提升效能,幫我們記憶,但這個動作並不是沒有代價的,最好的比喻就是:記憶吐司可以一直吃的嗎?

(然後大雄就拉肚子了)
那什麼時候會用到呢?當把 function 透過 props 傳遞,需要 reference equality:
function Foo({bar}) {
React.useEffect(() => {
const options = { bar }
buzz(options)
}, [bar])
return <div>foobar</div>
}
function Blub() {
const bar = React.useCallback(() => {}, [])
return <Foo bar={bar} />
}
可以看到 buzz 接受了一個 object,其中的 bar 是一個 function,而 bar 透過 prop 被傳遞進來,當 useEffect 知道 bar 變更時,就會執行。
這樣的情境下就能確保 bar 的傳遞時仍是一樣的 function,而當變更時能就進一步有不同的處理。
若本系列有錯誤的內容,歡迎隨時跟我告知。